package top.lingkang.finaloauth2.client.filter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.util.NestedServletException;
import top.lingkang.finaloauth2.client.base.Oauth2Authorize;
import top.lingkang.finaloauth2.client.base.ResourceExceptionHandler;
import top.lingkang.finaloauth2.client.config.ResourceServerProperties;
import top.lingkang.finaloauth2.client.error.BaseClientException;
import top.lingkang.finaloauth2.client.error.Oauth2HttpAccessException;
import top.lingkang.finaloauth2.client.error.Oauth2PermissionException;
import top.lingkang.finaloauth2.client.error.TokenInvalidException;
import top.lingkang.finaloauth2.client.http.ResourceContextHolder;
import top.lingkang.finaloauth2.client.http.ResourceServerHolder;
import top.lingkang.finaloauth2.utils.CommonUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

/**
 * @author lingkang
 * Created by 2022/3/23
 */
@RestControllerAdvice
public class ResourceServerFilter implements Filter {
    private ResourceServerProperties properties;

    public ResourceServerFilter(ResourceServerProperties properties) {
        this.properties = properties;
        properties.setExcludePath("/oauth2/**");
    }

    @Autowired
    private ResourceExceptionHandler resourceExceptionHandler;

    @Autowired
    private ResourceServerHolder resourceServerHolder;

    private List<String> cacheExcludePath = new ArrayList<>();
    private Map<String, Oauth2Authorize[]> cacheAuthorize = new HashMap<>();

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String path = request.getServletPath();
        ResourceContextHolder.setRequest(request);
        try {
            // 缓存的排除
            if (cacheExcludePath.contains(path)) {
                chain.doFilter(servletRequest, servletResponse);
                return;
            }

            // 排除
            for (String url : properties.getExcludePath()) {
                if (CommonUtils.matcher(url, path)) {
                    cacheExcludePath.add(path);
                    chain.doFilter(servletRequest, servletResponse);
                    return;
                }
            }

            // 检查令牌是否失效
            Set<String> hasRole = resourceServerHolder.getRole();

            // 缓存中的鉴权
            Oauth2Authorize[] oauth2Authorizes = cacheAuthorize.get(path);
            if (oauth2Authorizes != null) {
                for (Oauth2Authorize authorize : oauth2Authorizes) {
                    authorize.check(hasRole);
                }
                // 放行
                chain.doFilter(servletRequest, servletResponse);
                return;
            }

            List<Oauth2Authorize> authorizeList = new ArrayList<>();
            HashMap<String, Oauth2Authorize> authorize = properties.getCheckAuthorize().getAuthorize();
            for (Map.Entry<String, Oauth2Authorize> entry : authorize.entrySet()) {
                if (CommonUtils.matcher(entry.getKey(), path))
                    authorizeList.add(entry.getValue());
            }

            // 添加缓存
            cacheAuthorize.put(path, authorizeList.toArray(new Oauth2Authorize[authorizeList.size()]));

            // 角色权限检查
            for (Oauth2Authorize auth : authorizeList) {
                auth.check(hasRole);
            }

            // 放行
            chain.doFilter(servletRequest, servletResponse);
        } catch (Exception e) {
            if (e instanceof TokenInvalidException) {
                resourceExceptionHandler.tokenInvalidException(e, request, (HttpServletResponse) servletResponse);
            } else if (e instanceof Oauth2PermissionException) {
                resourceExceptionHandler.permissionException(e, request, (HttpServletResponse) servletResponse);
            } else if (e instanceof NestedServletException && e.getMessage() != null && e.getMessage().indexOf("Oauth2HttpAccessException") != -1) {
                resourceExceptionHandler.exception(new Exception("连接授权服务异常！", e), request, (HttpServletResponse) servletResponse);
            } else {
                throw e;
            }
        } finally {
            ResourceContextHolder.removeAll();
        }
    }

    /**
     * 异常处理
     *
     * @param e
     * @param request
     * @param response
     */
    @ExceptionHandler(value = {BaseClientException.class, Oauth2HttpAccessException.class})
    public void clientException(BaseClientException e, HttpServletRequest request, HttpServletResponse response) {
        resourceExceptionHandler.exception(e, request, response);
    }
}
